<?php

class Block
{
	/**
	 * constants
	 */
	/**
	 * used by static class
	 */
	const GATHER = 0;
	const RENDER = 1;
	
	/**
	 * used by instances
	 */
	const START = 0;
	const REPLACE = 1;
	const APPEND = 2;
	const PREPEND = 3;
	
	/**
	 * properties
	 */
	/**
	 * used by static class
	 */
	private static $blocks = array();
	private static $current = "";
	private static $mode = Block::GATHER;
	private static $types = array(
		self::START => "START",
		self::REPLACE => "REPLACE",
		self::APPEND => "APPEND",
		self::PREPEND => "PREPEND"
		);
	
	/**
	 * used by instances
	 */
	private $type;
	private $head;
	private $content;
	
	/**
	 * static methods
	 * - the static Block class follows a multiton design pattern
	 * - class instances are contained within an associative array
	 * - class instances are created using the start() static method where the name parameter is what the instance is associated with
	 */
	/**
	 * block-handlers
	 */
	public static function start($name)
	{
		if (self::$mode == Block::GATHER)
		{
			if (self::$current != "")
			{
				$backtrace = debug_backtrace();
				
				throw new Block_UnendedBlockException(sprintf("Cannot start a block while another block remains un-ended; requested from %s (%s)", $backtrace[0]["file"], $backtrace[0]["line"]));
			}
			else
			{
				if (array_key_exists($name, self::$blocks))
				{
					throw new Block_ExistingBlockException(sprintf("Block %s already exists", $name));
				}
				else
				{
					self::$blocks[$name] = new Block(Block::START);
					self::$current = $name;
					
					Buffer::start();
				}
			}
		}
		else
		{
			echo self::$blocks[$name] -> getProcessedContent();
			
			Buffer::start();
		}
	}
	public static function appendTo($name)
	{
		self::extendBlock($name, Block::APPEND);
	}
	public static function prependTo($name)
	{
		self::extendBlock($name, Block::PREPEND);
	}
	public static function replace($name)
	{
		self::extendBlock($name, Block::REPLACE);
	}
	private static function extendBlock($name, $type)
	{
		if (self::$mode == self::GATHER)
		{
			if (self::$current != "")
			{
				$backtrace = debug_backtrace();
				
				throw new Block_UnendedBlockException("Cannot append to a block while another block remains un-ended");
			}
			else
			{
				if (array_key_exists($name, self::$blocks) == false)
				{
					throw new Block_NonexistentBlockException(sprintf("Block %s does not exist", $name));
				}
				else
				{
					self::$current = $name;
					
					$node = self::$blocks[$name];
					while ($node -> getHead() != null)
					{
						$node = $node -> getHead();
					}
					$node -> setHead(new Block($type));
					
					Buffer::start();
				}
			}
		}
	}
	public static function end()
	{
		try
		{
			$content = Buffer::getClean();
		}
		catch (Buffer_AbstractException $e)
		{
			throw new Block_BufferException($e -> getMessage());
		}
		
		if (self::$mode == self::GATHER)
		{
			if (self::$current == "")
			{
				throw new Block_UnendedBlockException("No block to end");
			}
			else
			{
				$node = self::$blocks[self::$current];
				self::$current = "";
				
				while ($node -> getHead() != null)
				{
					$node = $node -> getHead();
				}
				
				$node -> setContent($content);
			}
		}
	}
	
	/**
	 * mode getters and setters
	 */
	public static function setMode($mode)
	{
		self::$mode = $mode;
	}
	public static function getMode()
	{
		return self::$mode;
	}
	
	/**
	 * block lsit getter
	 */
	public static function getBlockList()
	{
		return self::$blocks;
	}
	
	/**
	 * current block getter
	 */
	public static function getCurrentBlock()
	{
		return self::$current;
	}
	
	/**
	 * constant translation
	 */
	private static function getTypeNameFromType($type)
	{
		return self::$types[$type];
	}
	
	/**
	 * instance methods
	 * - a Block object a linked list of Blocks
	 * - though there are no add()/remove()/getByIndex() methods
	 * - ...the getHead() method is used for iteration, and setHead() for assignment
	 */
	/**
	 * constructor and clone magic methods
	 * - private so as to ensure objects are instantiated from only within the static class
	 */
	private function __construct($type)
	{
		$this -> type = $type;
	}
	private function __clone()
	{
	}
	
	/**
	 * getting processed content
	 */
	public function getProcessedContent()
	{
		$content = $this -> content;
		
		if ($this -> head != null)
		{
			$node = $this -> head;
			
			do
			{
				switch($node -> getType())
				{
					case Block::REPLACE:
						$content = $node -> getContent();
						
						break;
					case Block::APPEND:
						$content .= $node -> getContent();
						
						break;
					case Block::PREPEND;
						$content = $node -> getContent() . $content;
						
						break;
				}
			} while($node = $node -> getHead());
		}
		
		return $content;
	}
	
	/**
	 * type getter
	 */
	public function getType()
	{
		return $this -> type;
	}
	
	/**
	 * content setters and getters
	 */
	public function setContent($content)
	{
		$this -> content = $content;
		
		return $this;
	}
	public function getContent()
	{
		return $this -> content;
	}
	
	/**
	 * linked-list setters and getters
	 * - self-referential (simply put, a linked list)
	 * - so that all of the links can be contained within one object
	 */
	public function getHead()
	{
		return $this -> head;
	}
	public function setHead(Block $head)
	{
		$this -> head = $head;
		
		return $this;
	}
}